﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Net;

namespace Heimdallr.Net
{
    /// <summary> 
    /// An assistant class that provides easy access to properties of HTTP outgoing responses. 
    /// </summary>
    public sealed class HttpResponseContext : HttpOperationContext
    {
        #region Field

        private readonly HttpListenerResponse response;

        #endregion

        #region Property

        /// <summary>
        /// Gets and sets the content length header from the outgoing response.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Object is disposed.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The value specified for a set operation is less than zero.</exception>
        /// <exception cref="InvalidOperationException">The response is already being sent.</exception>
        public long ContentLength
        {
            get { return base.TryGet(() => this.response.ContentLength64); }
            set { base.TrySet(v => this.response.ContentLength64 = v, value); }
        }

        /// <summary>
        /// Gets and sets the content type header from the outgoing response.
        /// </summary> 
        /// <exception cref="ObjectDisposedException">Object is disposed.</exception>
        /// <exception cref="ArgumentException">The value specified for a set operation is an empty string ("").</exception>
        /// <exception cref="ArgumentNullException">The value specified for a set operation is null.</exception>
        public string ContentType
        {
            get { return base.TryGet(() => this.response.ContentType); }
            set { base.TrySet(v => this.response.ContentType = v, value); }
        }

        /// <summary>
        /// Gets and sets the etag header from the outgoing response.
        /// </summary>
        public string ETag
        {
            get { return base.TryGet(() => this.response.Headers[HttpResponseHeader.ETag]); }
            set { base.TrySet(v => this.response.Headers[HttpResponseHeader.ETag] = v, value); }
        }

        /// <summary>
        /// Gets and sets the last modified header of the outgoing response.
        /// </summary>
        public DateTime? LastModified
        {
            get { return base.TryGet(() => GetDateTimeValue(this.response.Headers[HttpResponseHeader.LastModified])); }
            set { base.TrySet(v => this.response.Headers[HttpResponseHeader.LastModified] = SetDateTimeValue(v), value); }
        }

        /// <summary>
        /// Gets and sets the location header from the outgoing response.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Object is disposed.</exception>
        /// <exception cref="ArgumentException">The value specified for a set operation is an empty string.</exception>
        public string Location
        {
            get { return base.TryGet(() => this.response.RedirectLocation); }
            set { base.TrySet(v => this.response.RedirectLocation = v, value); }
        }

        /// <summary>
        /// Gets and sets the status code of the outgoing response.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Object is disposed.</exception>
        /// <exception cref="InvalidEnumArgumentException">The value specified for a set operation is not valid.</exception>
        public HttpStatusCode StatusCode
        {
            get { return (HttpStatusCode)base.TryGet(() => this.response.StatusCode); }
            set
            {
                if (!Enum.IsDefined(typeof(HttpStatusCode), value))
                    throw new InvalidEnumArgumentException("StatusCode", (int)value, typeof(HttpStatusCode));

                base.TrySet(v => this.response.StatusCode = v, (int)value);
            }
        }

        /// <summary>
        /// Gets and sets the status description of the outgoing response.
        /// </summary>
        /// <exception cref="ArgumentNullException">The value specified for a set operation is null.</exception>
        /// <exception cref="ArgumentException">The value specified for a set operation contains non-printable characters.</exception>
        public string StatusDescription
        {
            get { return base.TryGet(() => this.response.StatusDescription); }
            set { base.TrySet(v => this.response.StatusDescription = v, value); }
        }

        /// <summary>
        /// Gets the headers from the outgoing response.
        /// </summary> 
        public WebHeaderCollection Headers
        {
            get { return base.TryGet(() => this.response.Headers); }
        }

        /// <summary>
        /// Gets or sets whether the response uses chunked transfer encoding.
        /// </summary>
        public bool SendChunked
        {
            get { return base.TryGet(() => this.response.SendChunked); }
            set { base.TrySet(v => this.response.SendChunked = v, value); }
        }

        #endregion

        #region Constructor

        internal HttpResponseContext(string username, EndPoint remoteEp, Uri requestUri, HttpListenerResponse response)
            : base(username, remoteEp, requestUri)
        {
            this.response = response;
        }

        #endregion

        #region Method

        /// <summary>
        /// Gets a <see cref="Stream"/> object to which a response can be written.
        /// </summary>
        /// <returns>A <see cref="Stream"/> object to which a response can be written.</returns>
        /// <exception cref="ObjectDisposedException">Object is disposed.</exception>
        public Stream GetResponseStream()
        {
            return base.TryGet(() => this.response.OutputStream);
        }

        #endregion

        #region IDisposable Member

        /// <summary>
        /// Disposes this object.
        /// </summary>
        public override void Dispose()
        {
            base.Dispose();

            try
            {
                this.response.Close();
            }
#if DEBUG
            catch (Exception error)
            {
                Debug.WriteLine(error);
#else
            catch
            {
#endif
            }
        }

        #endregion
    }
}